﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Xml;

namespace AppFuscator.MapDecoder
{
    internal struct NameSpaceType
    {
        public string NameSpaceName { get; set; }

        public string TypeName { get; set; }
    }

    internal class XmlReader
    {
        private class DefaultAssemble
        {
            public Module NewMod { get; set; }

            public Module OldMod { get; set; }

            public Module CurrNewMod { get; set; }

            public Module CurrOldMod { get; set; }

            public DefaultAssemble(Project project)
            {
                var newAsm = new Assemble("NewAssemble", project, null);
                var oldAsm = new Assemble("OldAssemble", project, newAsm);
                newAsm.NodeLink = oldAsm; //обвление узла, вначале я не могу добавить старый узел, т.к. ещё не инициализировал его
                NewMod = new Module("NewModule", newAsm, null);
                OldMod = new Module("OldModule", oldAsm, NewMod);
                NewMod.NodeLink = OldMod;
                project.AddAssembleNewName(newAsm); //Добавление ссылок на поддеревья в 1 проект
                project.AddAssembleOldName(oldAsm);
                var currentNewAsm = project.AssembleListNewName.Last();
                var currentOldAsm = project.AssembleListOldName.Last();
                currentNewAsm.AddModule(NewMod);
                currentOldAsm.AddModule(OldMod);
                CurrNewMod = project.AssembleListNewName.Last().ModuleList.Last();
                CurrOldMod = project.AssembleListOldName.Last().ModuleList.Last();
            }
        }

        public Project FillTree(string filePath)
        {
            var project = new Project("MyProject");
            
            XmlNodeList parent = null;
            var xmlDocument = new XmlDocument();
            xmlDocument.Load(filePath);
            if (xmlDocument.DocumentElement != null)
            {
                var xmlElement = xmlDocument.DocumentElement["Childs"];
                if (xmlElement != null)
                    parent = xmlElement.ChildNodes;
            }
            else
            {
                return null;
            }

            foreach (XmlNode node in parent) //цикл для добавления корней Type, внутри вызывается функция для добавления методов и подметодов
            {
                string nodeType = node.Attributes["NodeType"].Value;
                string newName = node.Attributes["NewName"].Value;
                string oldName = node.Attributes["OldName"].Value;
                if (nodeType == "Assembly")
                {
                    SetAssemble(newName, project, oldName, node);
                }
                if (nodeType == "Type")
                {
                    DefaultAssemble defaultAssemble = null;
                    if (defaultAssemble == null)
                    {defaultAssemble = new DefaultAssemble(project);
                    }
                    FillTypeParent(parent, defaultAssemble.NewMod, defaultAssemble.OldMod,
                        defaultAssemble.CurrNewMod, defaultAssemble.CurrOldMod);
                    break;
                }
            }
            return project;
        }

        private void SetAssemble(string newName, Project project, string oldName, XmlNode node)
        {
            var newAsm = new Assemble(newName, project, null);
            var oldAsm = new Assemble(oldName, project, newAsm);
            newAsm.NodeLink = oldAsm; //обновление узла, вначале я не могу добавить старый узел, т.к. ещё не инициализировал его
            Module newMod = new Module(newName, newAsm, null);
            Module oldMod = new Module(oldName, oldAsm, newMod);
            newMod.NodeLink = oldMod;
            project.AddAssembleNewName(newAsm); //Добавление ссылок на поддеревья в 1 проект
            project.AddAssembleOldName(oldAsm);
            var currentNewAsm = project.AssembleListNewName.Last();
            var currentOldAsm = project.AssembleListOldName.Last();
            currentNewAsm.AddModule(newMod);
            currentOldAsm.AddModule(oldMod);
            Module currentNewMod = project.AssembleListNewName.Last().ModuleList.Last();
            Module currentOldMod = project.AssembleListOldName.Last().ModuleList.Last();
            if (node.ChildNodes.Count > 0)
            {
                FillTypeParent(node.ChildNodes[0].ChildNodes, newMod, oldMod, currentNewMod, currentOldMod);
            }
        }

        private void FillTypeParent(XmlNodeList nodeList, Module newMod, Module oldMod, Module currentNewMod, Module currentOldMod)
        {
            int nodeNomber = 0;
            foreach (XmlNode node in nodeList)
            {
                string newName = node.Attributes["NewName"].Value;
                string oldName = node.Attributes["OldName"].Value;

                var nameSpaceType = ExtractNameSpace(newName);//Парсю неймспейс и имя типа
                var newType = new Type(nameSpaceType.TypeName, newMod, null, nameSpaceType.NameSpaceName);
                nameSpaceType = ExtractNameSpace(oldName);
                var oldType = new Type(nameSpaceType.TypeName, oldMod, newType, nameSpaceType.NameSpaceName);
                newType.NodeLink = oldType;
                currentNewMod.AddType(newType); //Добавление корней в конец списка Type
                currentOldMod.AddType(oldType);
                var newDefaultType = currentNewMod.DefaultType;
                var oldDefaultType = currentOldMod.DefaultType;

                if (node.ChildNodes.Count > 0) //если у корня есть узлы
                {
                    //передаю незаполненный экземпляр корня и считанные с хмл узлы, результат - заполненный корень

                    Parser(node.ChildNodes[0].ChildNodes,
                        newType,
                        oldType,
                        newDefaultType,
                        oldDefaultType);
                    currentNewMod.TypeList[nodeNomber] = newType;
                    currentOldMod.TypeList[nodeNomber] = oldType;
                }
                ++nodeNomber;
            }
        }

        private void Parser(IEnumerable nodes, Type newType, Type oldType, Type newDefaultType, Type oldDefaultType)
        {
            foreach (XmlNode node in nodes)
            {
                //обход каждого узла корня (корнем является Type, узлами - его методы, свойства и прочее)
                string nodeType = node.Attributes["NodeType"].Value;
                string newName = node.Attributes["NewName"].Value;
                string oldName = node.Attributes["OldName"].Value;
                string decomposition = "";
                if (node.Attributes["IsDecomposited"] != null)
                {
                    decomposition = node.Attributes["IsDecomposited"].Value;
                }

                if (nodeType == "Field")
                {
                    var newField = new Field(newName, newType, null);
                    var oldField = new Field(oldName, oldType, newField);
                    newType.AddField(newField);
                    oldType.AddField(oldField);
                }
                if (nodeType == "Method")
                {
                    string newReturn = ParseText(@"[\w\.<>_]+(?= )", newName); //парсю из всей строки только тип возвращаемого значения
                    string oldReturn = ParseText(@"[\w\.<>_]+(?= )", oldName);
                    string newParamName = ParseText(@"(?<=\()[\w\.\, _<>&\[\]\`\/]+(?=\))", newName); //вся строка внутри скобок(параметры одной строкой)
                    string oldParamName = ParseText(@"(?<=\()[\w\.\, _<>&\[\]\`\/]+(?=\))", oldName);
                    string newMethodName = ParseText(@"(?<=\ )[\w\.\`\, _<>]+(?=\()", newName); //имя метода
                    string oldMethodName = ParseText(@"(?<=\ )[\w\.\`\, _<>]+(?=\()", oldName);

                    var newMethod = new Method(newMethodName, newType, null) { ReturnTypeName = newReturn };
                    var oldMethod = new Method(oldMethodName, oldType, newMethod) { ReturnTypeName = oldReturn };
                    newMethod.NodeLink = oldMethod;
                    SplitParams(newParamName, newMethod, null); //добавление параметров к методу, если они есть
                    SplitParams(oldParamName, oldMethod, newMethod.Parameters);
                    var paramNomber = 0;
                    foreach (var temp in oldMethod.Parameters) //связываю параметры узлов newMethod и oldMethod
                    {
                        newMethod.Parameters[paramNomber].NodeLink = temp.NodeLink;
                        paramNomber++;
                    }
                    if (decomposition.ToLower() == "true")//todo
                    {
                        newDefaultType.AddMethod(newMethod);
                        oldDefaultType.AddMethod(oldMethod);
                    }
                    else
                    {
                        newType.AddMethod(newMethod);
                        oldType.AddMethod(oldMethod);
                    }
                }

                if (nodeType == "Property")
                {
                    var newProperty = new Property(newName, newType, null);
                    var oldProperty = new Property(oldName, oldType, newProperty);
                    newProperty.NodeLink = oldProperty;
                    newType.AddProperty(newProperty);
                    oldType.AddProperty(oldProperty);
                }
                if (nodeType == "Event")
                {
                    var newEvent = new Event(newName, newType, null);
                    var oldEvent = new Event(oldName, oldType, newEvent);
                    newEvent.NodeLink = oldEvent;
                    newType.AddEvent(newEvent);
                    oldType.AddEvent(oldEvent);
                }
                if (nodeType == "Type") //вложенный Type
                {
                    var nameSpaceType = ExtractNameSpace(newName);
                    var newNested = new Type(nameSpaceType.TypeName, newType, null, nameSpaceType.NameSpaceName);
                    nameSpaceType = ExtractNameSpace(oldName);
                    var oldNested = new Type(nameSpaceType.TypeName, oldType, newType, nameSpaceType.NameSpaceName);
                    newNested.NodeLink = oldNested;
                    newType.AddType(newNested);
                    oldType.AddType(oldNested);
                    if (node.ChildNodes.Count > 0)
                    {
                        Parser(node.ChildNodes[0].ChildNodes,
                           newNested,
                           oldNested,
                           newDefaultType,
                           oldDefaultType);
                        newType.TypeList[newType.TypeList.Count - 1] = newNested;
                        oldType.TypeList[newType.TypeList.Count - 1] = oldNested;
                    }
                }
            }
        }

        private string ParseText(string pattern, string text)
        {
            var regex = new Regex(pattern);
            return regex.Match(text).Value;
        }

        private void SplitParams(string paramName, Method method, IList<Parameter> nodeLink)
        {
            //paramName строка вида "System.Int32, System.String"
            int paramNomber = 0;
            string[] separator = new[] { "," };
            string[] newSpl = paramName.Split(separator, StringSplitOptions.RemoveEmptyEntries);
            if (newSpl.Length <= 0) return;
            foreach (var spl in newSpl)
            {
                int indexEnd = spl.LastIndexOf('/') + 1; //Если найду слеш, то удалю все до слеша, иначе удалю всё до последней точки
                if (indexEnd == 0)
                    indexEnd = spl.LastIndexOf('.') + 1;
                int length = spl.Length - indexEnd;
                var parseName = spl.Substring(indexEnd, length);
                var splittedParam = parseName.TrimStart(' ').Split(' ');
                if (splittedParam.Length == 2)
                {
                    method.Parameters.Add(nodeLink != null //добавление типа и имени параметра
                                              ? new Parameter(splittedParam[0], splittedParam[1], method, nodeLink[paramNomber])
                                              : new Parameter(splittedParam[0], splittedParam[1], method, null));
                }
                if (splittedParam.Length == 1)
                {
                    method.Parameters.Add(nodeLink != null//добавление только типа параметра
                                              ? new Parameter(splittedParam[0], method, nodeLink[paramNomber])
                                              : new Parameter(splittedParam[0], method, null));
                }
                paramNomber++;
            }
        }

        private NameSpaceType ExtractNameSpace(string text)
        {
            NameSpaceType nameSpaceType = new NameSpaceType();
            if (text.IndexOf('.') != -1)
            {
                nameSpaceType.NameSpaceName = text.Substring(0, text.LastIndexOf('.'));
                nameSpaceType.TypeName = text.Substring(text.LastIndexOf('.') + 1, text.Length - text.LastIndexOf('.') - 1);
                return nameSpaceType;
            }
            else
            {
                nameSpaceType.NameSpaceName = "";
                nameSpaceType.TypeName = text;
                return nameSpaceType;
            }
        }
    }
}